/*
 * Copyright 2022 The Open Islands Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.openislands.oi.manager.union;


import org.springframework.context.ApplicationContext;

import java.util.function.Function;

/***
 * @param <Target>  {@link #handler} handle(Target,Params) first params class
 * @param <Params>  {@link #handler} handle(Target,Params) second params class
 * @param <Result>  {@link #nextParams}
 */
public class UnionHandlerContext<Target, Params, Result> {
    /***
     * {@link #handler} return value after handled
     * {@link #next} params that can be used
     */
    private Result nextParams;

    /***
     * {@link #handler} handle(Target,$) first params class
     * Get target by {@link UnionPipelineManager#ordinaryClass} or {@link ApplicationContext#getBean}
     */
    private final Class<Target> targetClass;

    /***
     * The actual handler that needs to be used
     */
    private final UnionHandler<Target, Params, Result> handler;

    /***
     * prev context {@link #addNext(Class, UnionHandler)}
     */
    private UnionHandlerContext<?, ?, Params> prev;

    /***
     * next context {@link #addNext(Class, UnionHandler)}
     */
    private UnionHandlerContext<?, Result, ?> next;

    /***
     * Whether rollback is required, when false will prevent throwing an exception to prev context
     */
    private boolean rollback = true;

    /***
     * The rollback handler that needs to be used
     */
    private UnionHandler<Target, Params, Result> rollbackHandler;

    public UnionHandlerContext(Class<Target> targetClass, UnionHandler<Target, Params, Result> handler) {
        this.handler = handler;
        this.targetClass = targetClass;
    }

    public Class<Target> getTargetClass() {
        return targetClass;
    }

    public Result getNextParams() {
        return nextParams;
    }

    public Params getParams() {
        return prev != null ? prev.getNextParams() : null;
    }

    public UnionHandler<Target, Params, Result> getHandler() {
        return handler;
    }

    public UnionHandlerContext<?, ?, Params> getPrev() {
        return prev;
    }

    public UnionHandlerContext<?, Result, ?> getNext() {
        return next;
    }

    public boolean canRollback() {
        return rollback;
    }

    public UnionHandler<Target, Params, Result> getRollbackHandler() {
        return rollbackHandler;
    }

    public void setNextParams(Result nextParams) {
        this.nextParams = nextParams;
    }

    public <NT, NR> UnionHandlerContext<NT, Result, NR> addNext(Class<NT> mapperClass, UnionHandler<NT, Result, NR> handler) {
        UnionHandlerContext<NT, Result, NR> nextContext = new UnionHandlerContext<>(mapperClass, handler);
        nextContext.prev = this;
        this.next = nextContext;
        return nextContext;
    }

    public <NT, NR> UnionHandlerContext<NT, Result, NR> addNextS(Class<NT> mapperClass, UnionHandler.SingleHandler<NT, NR> handler) {
        return this.addNext(mapperClass, (target, params) -> handler.handle(target));
    }

    public UnionHandlerContext<Target, Params, Result> rollback(boolean rollback) {
        this.rollback = rollback;
        return this;
    }

    public UnionHandlerContext<Target, Params, Result> rollback(UnionHandler<Target, Params, Result> rollbackHandler) {
        this.rollbackHandler = rollbackHandler;
        return this;
    }

    public <NT, NR> UnionHandlerContext<NT, Result, NR> castResult(Function<Result, NR> function) {
        return this.addNext(UnionPipeline.emptyDefine(), ($, result) -> function.apply(result));
    }

    public <NT> UnionHandlerContext<NT, Result, Result> assertResult(Function<Result, Boolean> assertFunction, RuntimeException exception) {
        return this.addNext(UnionPipeline.emptyDefine(), ($, result) -> {
            if (result != null && !assertFunction.apply(result)) {
                throw exception;
            }
            return result;
        });
    }
}